use std::ops::Mul;

use super::vector4::Vector4;
use std::fmt;

#[derive(Copy, Clone)]
pub struct Matrix4 {
    col0: Vector4,     // the 1st column of Matrix4
    col1: Vector4,     // the 2nd column of Matrix4
    col2: Vector4,     // the 3rd column of Matrix4
    col3: Vector4      // the 4th column of Matrix4
}

impl Matrix4 {
    pub fn identity() -> Self {
        Self {
            col0: Vector4(1.0, 0.0, 0.0, 0.0),
            col1: Vector4(0.0, 1.0, 0.0, 0.0),
            col2: Vector4(0.0, 0.0, 1.0, 0.0),
            col3: Vector4(0.0, 0.0, 0.0, 1.0)
        }
    }

    pub fn scale(x: f32, y: f32, z: f32) -> Self {
        Self {
            col0: Vector4(x, 0.0, 0.0, 0.0),
            col1: Vector4(0.0, y, 0.0, 0.0),
            col2: Vector4(0.0, 0.0, z, 0.0),
            col3: Vector4(0.0, 0.0, 0.0, 1.0)
        }
    }

    pub fn translation(x: f32, y: f32, z: f32) -> Self {
        Self {
            col0: Vector4(1.0, 0.0, 0.0, 0.0),
            col1: Vector4(0.0, 1.0, 0.0, 0.0),
            col2: Vector4(0.0, 0.0, 1.0, 0.0),
            col3: Vector4(x  , y  , z  , 1.0)
        }
    }

    pub fn translation_vec(translate: Vector3) -> Self {
        Self::translation(translate.0, translate.1, translate.2)
    }

    pub fn orthogonal(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Self {
        let dx = right - left;
        let dy = top - bottom;
        let dz = far - near;

        Self {
            col0: Vector4(2.0 / dx, 0.0, 0.0, 0.0),
            col1: Vector4(0.0, 2.0 / dy, 0.0, 0.0),
            col2: Vector4(0.0, 0.0, -2.0 / dz, 0.0),
            col3: Vector4(-(right + left) / dx, -(top + bottom) / dy, -(far + near) / dz, 1.0)
        }
    }

    pub fn row0(&self) -> Vector4 {
        Vector4(self.col0.0, self.col1.0, self.col2.0, self.col3.0)
    }

    pub fn row1(&self) -> Vector4 {
        Vector4(self.col0.1, self.col1.1, self.col2.1, self.col3.1)
    }

    pub fn row2(&self) -> Vector4 {
        Vector4(self.col0.2, self.col1.2, self.col2.2, self.col3.2)
    }

    pub fn row3(&self) -> Vector4 {
        Vector4(self.col0.3, self.col1.3, self.col2.3, self.col3.3)
    }
}

use glium::uniforms::{AsUniformValue, UniformValue};
use crate::math::vector3::Vector3;

impl AsUniformValue for Matrix4 {
    fn as_uniform_value(&self) -> UniformValue {
        UniformValue::Mat4([
            self.col0.as_array(),
            self.col1.as_array(),
            self.col2.as_array(),
            self.col3.as_array()
        ])
    }
}

impl fmt::Display for Matrix4 {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}\n{}\n{}\n{}", self.col0, self.col1, self.col2, self.col3)
    }
}

impl Mul for Matrix4 {
    type Output = Self;
    fn mul(self, rhs: Self) -> Self {
        Self {
            col0: Vector4(self.row0().dot(rhs.col0), self.row0().dot(rhs.col1), self.row0().dot(rhs.col2), self.row0().dot(rhs.col3)),
            col1: Vector4(self.row1().dot(rhs.col0), self.row1().dot(rhs.col1), self.row1().dot(rhs.col2), self.row1().dot(rhs.col3)),
            col2: Vector4(self.row2().dot(rhs.col0), self.row2().dot(rhs.col1), self.row2().dot(rhs.col2), self.row2().dot(rhs.col3)),
            col3: Vector4(self.row3().dot(rhs.col0), self.row3().dot(rhs.col1), self.row3().dot(rhs.col2), self.row3().dot(rhs.col3))
        }
    }
}